
# 包含其他makefile
# 和 C 语言中的 #inlude 作用相似
# 如果当前目录下没有找到,那么,make 还会在下面的几个目录下找:
# 1. 如果 make 执行时,有 -I 或 --include-dir 参数,那么 make 就会在这个参数所指定的目录下去寻找.
# 2. 如果目录 <prefix>/include (一般是:/usr/local/include 或 /usr/include )存在的话,make 也会去找.
include 01_family/makefile

# 无论 include 过程中出现什么错误, 都不要报错继续执行
sinclude 01_family/makefile
-include 01_family/makefile










# command 规则,同行封号, 换行 Tab
hello1: ; echo 1
hello2:
	echo 2










# make 支持三个通配符：
# * ? ~
# * 表示任意长度的任意字符(包括空字符)
# ? 表示一个任意字符, 且只能替代一个字符
# [] 表示一个字符集合, 可以匹配其中任意一个字符
#    例如[abc].c表示可以匹配a.c，b.c或c.c
#    还可以使用-表示一个字符范围, 如[a-z].c表示可以匹配所有小写字母开头的.c文件
# ~ 表示用户的主目录, 如果~后接用户名, 表示该用户的主目录
#   例如, ~/foo.c表示当前用户的主目录下的foo.c文件
#   ~bob/bar.c表示用户bob的主目录下的bar.c文件
objects = *.o

# 让通配符在变量中展开
# $ (wildcard <PATTERN…>)
# wildcard函数用于展开通配符
# <PATTERN…>是一个或多个文件名模式, 用空格分隔
objects := $(wildcard *.o)

# patsubst函数将所有.c文件的后缀替换为.o
# $ (patsubst <pattern>, <replacement>, <text>)
# 它的功能是在<text>中查找符合<pattern>的单词，并用<replacement>替换它们
# 单词的分隔符是空格, Tab或换行符, 通配符 % 表示任意长度的字符串
# 如果<text>是一个变量, 那么它的值会被展开后再进行替换
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
	cc -o foo $(objects)









# 文件搜寻
# Makefile 文件中的特殊变量 VPATH
# 目录src和../headers, make会按照这个顺序进行搜索, 目录由冒号分隔
VPATH = src:../headers

# 另一个设置文件搜索路径的方法是使用 make 的vpath关键字(全小写)
# 这不是变量, 这是一个 make 的关键字, 这和上面提到的那个 VPATH 变量很类似, 但是它更为灵活
# vpath <pattern> <directories> 为符合模式 <pattern> 的文件指定搜索目录 <directories>
# vpath <pattern> 清除符合模式 <pattern> 的文件的搜索目录
# vpath 清除所有已被设置好了的文件搜索目录
# vapth 使用方法中的 <pattern> 需要包含 % 字符, % 的意思是匹配零或若干字符
vpath %.h ../headers # 要求 make 在../headers目录下搜索所有以 .h 结尾的文件

# 可以连续地使用 vpath 语句, 以指定不同搜索策略
# 如果连续的 vpath 语句中出现了相同的 <pattern> 或是被重复了的 <pattern>
# 那么make 会按照 vpath 语句的先后顺序来执行搜索
vpath %.c foo
vpath % blish
vpath %.c bar
# 其表示 .c 结尾的文件, 先在 foo 目录, 然后是blish, 最后是 bar 目录









# 伪目标不存在真实的文件支撑, 它的取名不能和文件名重名
# 为了避免和文件重名的这种情况, 我们可以使用一个特殊的标记 ".PHONY" 来显式地指明
# 一个目标是"伪目标", 向 make 说明, 不管是否有这个文件, 这个目标就是"伪目标"
.PHONY : clean
clean :
	rm *.o temp

# 伪目标可以指定依赖, 也可以成为默认目标
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
	cc -o prog1 prog1.o utils.o
prog2 : prog2.o
	cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
	cc -o prog3 prog3.o sort.o utils.o

# 伪目标可以成为 被依赖
PHONY : cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
	rm program
cleanobj :
	rm *.o
cleandiff :
	rm *.diff









# 多目标
# $@ 表示目标的集合, 就像一个数组, $@ 依次取出目标, 并执于命令
# $(subst FROM,TO,TEXT)
# 把字符串TEXT中的FROM替换成TO, 并返回替换后的字符串
bigoutput littleoutput : text.g
	generate text.g -$(subst output,,$@) > $@
# 等价于
bigoutput : text.g
	generate text.g -big > bigoutput
littleoutput : text.g
	generate text.g -little > littleoutput









# 静态模式
# makefile中的静态模式是一种特殊的规则, 用于定义多个目标文件
# 它们的依赖文件和生成命令都遵循相同的模式
# 静态模式可以简化makefile的编, 提高编译的效率和灵活性
<targets ...> : <target-pattern> : <prereq-patterns ...>
	<commands>
	...
# targets 定义了一系列的目标文件, 可以有通配符, 是目标的一个集合
# target-pattern 是指明了 targets 的模式, 也就是的目标集模式
# prereq-patterns 是目标的依赖模式, 它对 target-pattern 形成的模式再进行一次依赖目标的定义
#
# 如果我们的 <target-pattern> 定义成 %.o, 意思是我们的 <target>集合中都是以 .o 结尾的
# 而如果我们的 <prereq-patterns> 定义成 %.c, 意思是对 <target-pattern> 所形成的目标集进行二次定义
# 其计算方法是取 <target-pattern> 模式中的 % (也就是去掉了 .o 这个结尾), 并为其加上 .c 这个结尾, 形成的新集合
# 例子 1
objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@
# $< 表示第一个依赖文件, $@ 表示目标集
# 上式中等价于
foo.o : foo.c
	$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
	$(CC) -c $(CFLAGS) bar.c -o bar.o

# 例子 2
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
	emacs -f batch-byte-compile $<









# 自动生成依赖
# gcc -M 和 gcc -MM 会自动生产目标和依赖关系
%.d: %.c
	@set -e; rm -f $@; \
	$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
	sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
	rm -f $@.$$$$
# 这个规则的意思是, 所有的. d文件依赖于. c文件, rm -f $@ 的意思是删除所有的目标, 也就是.d文件
# 第二行的意思是, 为每个依赖文件 $<, 也就是 .c 文件生成依赖文件, $@表示模式 %.d文件
# 如果有一个C文件是name. c, 那么%就是name, $$$$意为一个随机编号, 第二行生成的文件有可能是"name. d.12345"
# 第三行使用sed命令做了一个替换
# 这个模式要做的事就是在编译器生成的依赖关系中加入 .d 文件的依赖, 即把依赖关系
# main.o : main.c defs.h
# 转成:
# main.o main.d : main.c defs.h
